
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#if __linux
#include <dirent.h>
#include <getopt.h>
#include <libconfig.h>
#include <pcre.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#include "config.h"
#include "gt_list.h"
#include "gt_option.h"

#define APP_VERSION "0.0.1"

// ok
static void usage(int status) {
  FILE *out = status == 0 ? stdout : stderr;
  fprintf(out, "usage: greptui [-h|--help] [-v|--version]\n");
  fprintf(out, "       greptui [--<parser>] [<parser-options>] -- <pattern> "
               "[<path>]\n");
  fprintf(out,
          "       greptui [--<parser>=<parser-options>] <pattern> [<path>]\n");
  fprintf(out, "\n");
  fprintf(out, "options:\n");
  fprintf(out, " -h, --help     display this message\n");
  fprintf(out, " -v, --version  show greptui version\n");
  fprintf(out, "\n");
  fprintf(out, "parser:\n");
  fprintf(out, " --nat[=<nat-options>]       use greptui's native search "
               "implementation with <nat-options>\n");
  fprintf(out, " --ag[=<ag-options>]         use ag aka sliver searcher as "
               "parser\n");
  fprintf(out,
          " --git[=<git-grep-options>]  use git-grep as parser (works only "
          "within GIT repositories)\n");
  fprintf(out, "\n");
  fprintf(out, "nat-options:\n");
  fprintf(out, " -i         ignore case distinctions in pattern\n");
  fprintf(out, " -r         raw mode\n");
  fprintf(out, " -t <type>  look into files with specified <type>\n");
  fprintf(out, " -I <name>  ignore file/dir with specified <name>\n");
  fprintf(out, " -e         pattern is a regular expression\n");
  exit(status);
}

// ok
static void display_version(void) {
  fprintf(stdout, "version: %s\n", APP_VERSION);
  exit(0);
}

// tocheck
#ifndef read_config // ignore for testing
static void read_config(configuration_t *config, option_t *option) {
  const char *specific_file;
  const char *extension;
  const char *ignore;
  const char *buffer;
  char *ptr;
  char *buf = NULL;

  load_config(config);
  config_t cfg = get_config(config);
  if (!config_lookup_string(&cfg, "editor", &buffer)) {
    fprintf(stderr, "greptui.rc: no editor string found\n");
    exit(-1);
  }
  strncpy(option->editor, buffer, LINE_MAX - 1);

  if (config_lookup_string(&cfg, "default_parser", &buffer)) {
    if (!strncmp(buffer, "ag", 2))
      option->search_type = AG_SEARCH;
    else if (!strncmp(buffer, "git", 3))
      option->search_type = GIT_SEARCH;
    else
      option->search_type = GREP_SEARCH;
  }
  auto check_helper = [&](const char *cmd_str, search_type_t search_type) {
    if (config_lookup_string(&cfg, cmd_str, &buffer)) {
      strncpy(option->parser_cmd[search_type], buffer, LINE_MAX - 1);
    } else {
      fprintf(stderr, "greptui.rc: no %s string found\n", cmd_str);
      exit(-1);
    }
  };

  //   if (config_lookup_string(&cfg, "ag_cmd", &buffer)) {
  //     strncpy(opt->parser_cmd[AG_SEARCH], buffer, LINE_MAX - 1);
  //   } else {
  //     fprintf(stderr, "greptui.rc: no ag_cmd string found\n");
  //   }
  check_helper("ag_cmd", AG_SEARCH);
  check_helper("git_cmd", GIT_SEARCH);

  if (config_lookup_string(&cfg, "files", &specific_file)) {
    option->specific_file = create_list();
    ptr = strtok_r((char *)specific_file, " ", &buf);

    while (ptr != NULL) {
      add_element(&option->specific_file, ptr);
      ptr = strtok_r(NULL, " ", &buf);
    }
  } else {
    fprintf(stderr, "greptui.rc: no files string found\n");
    exit(-1);
  }

  if (config_lookup_string(&cfg, "extensions", &extension)) {
    option->extension = create_list();
    ptr = strtok_r((char *)extension, " ", &buf);
    while (ptr != NULL) {
      add_element(&option->extension, ptr);
      ptr = strtok_r(NULL, " ", &buf);
    }
  } else {
    fprintf(stderr, "greptui.rc: no extensions string found\n");
    exit(-1);
  }

  if (config_lookup_string(&cfg, "ignore", &ignore)) {
    option->ignore = create_list();
    ptr = strtok_r((char *)ignore, " ", &buf);
    while (ptr != NULL) {
      add_element(&option->ignore, ptr);
      ptr = strtok_r(NULL, " ", &buf);
    }
  } else {
    fprintf(stderr, "greptui.rc: no ignore string found\n");
    exit(-1);
  }

  config_destroy(&cfg);
}
#endif

// ok
static void parse_program_search_args(option_t *option, int argc,
                                      char *argv[]) {
  opterr = 1;
  optind = 0;

  int opt = 0;
  int clear_extensions = 0;
  int clear_ignores = 0;

  while ((opt = getopt(argc, argv, "eit:rI:")) != -1) {
    switch (opt) {
    case 'i':
      option->incase_option = 1;
      break;
    case 't':
      if (!clear_extensions) {
        free_list(&option->specific_file);
        free_list(&option->extension);
        option->extension_option = 1;
        clear_extensions = 1;
      }
      add_element(&option->extension, optarg);
      break;
    case 'I':
      if (!clear_ignores) {
        free_list(&option->ignore);
        option->ignore_option = 1;
        clear_ignores = 1;
      }
      add_element(&option->ignore, optarg);
      break;
    case 'r':
      option->raw_option = 1;
      break;
    case 'e':
      option->regexp_option = 1;
      break;
    default:
      free_option(option);
      free(argv);
      usage(-1);
      break;
    }
  }
}

// ok ?
static void parse_args(option_t *option, int argc, char *argv[]) {
  static struct option long_options[] = {
      {"help", no_argument, 0, 'h'},      {"version", no_argument, 0, 'v'},
      {"nat", optional_argument, 0, 'n'}, {"ag", optional_argument, 0, 'a'},
      {"git", optional_argument, 0, 'g'}, {0, 0, 0, 0}};

  int arg_count = argc;
  char **args = (char **)calloc(argc, sizeof(*args));
  int i;
  int first_arg{0};
  DIR *dirp = (DIR *)opendir(option->directory);

  for (i = 0; i < argc; ++i) {
    args[i] = argv[i];
  }

  optind = 0;
  opterr = 0;

  int opt = 0;
  while (opt != -1) {
    opt = getopt_long(arg_count, args, "-hv", long_options, NULL);

    int current_index = optind - 1;
    switch (opt) {
    case 'h':
      free_option(option);
      free(args);
      usage(0);
      break;
    case 'v':
      free_option(option);
      free(args);
      display_version();
      break;

    case 'n': {
      if (current_index != 1)
        goto free_and_usage;

      option->search_type = GREP_SEARCH;

      if (optarg != NULL) {
        strcpy(option->parser_options, optarg);
        opt = -1;
      }

      argv[current_index] = NULL;
    } break;

    case 'a': {
      if (current_index != 1)
        goto free_and_usage;

      option->search_type = AG_SEARCH;

      if (optarg != NULL) {
        strcpy(option->parser_options, optarg);
        opt = -1;
      }

      argv[current_index] = NULL;
    } break;

    case 'g': {
      if (current_index != 1)
        goto free_and_usage;

      option->search_type = GIT_SEARCH;

      if (optarg != NULL) {
        strcpy(option->parser_options, optarg);
        opt = -1;
      }

      argv[current_index] = NULL;
    } break;

    case 1: {
      if (current_index == 1) {
        optind--;
        opt = -1;
        continue;
      }
    }
    case '?': {
      if (option->search_type == GREP_SEARCH)
        continue;
      if (strlen(option->parser_options) > 0)
        strcat(option->parser_options, " ");
      strcat(option->parser_options, argv[current_index]);
      continue;
    } break;
    }
  }

  if (option->search_type == GREP_SEARCH) {
    arg_count = 0;
    int i;
    for (i = 0; i < argc; ++i)
      if (argv[i])
        args[arg_count++] = argv[i];

    if (strlen(option->parser_options) > 1) {
      int new_argc = arg_count;
      char *arg = option->parser_options;
      for (i = 0; i < strlen(arg); ++i) {
        if (arg[i] == ' ')
          new_argc++;
      }

      new_argc++;

      char **new_args = (char **)calloc(new_argc, sizeof(*new_args));
      new_args[0] = argv[0];

      new_argc = 1;
      while ((arg = strtok(arg, " ")) != NULL) {
        new_args[new_argc++] = arg;
        arg += strlen(arg) + 1;
      }

      for (i = 1; i < arg_count; ++i) {
        new_args[new_argc++] = args[i];
      }

      free(args);
      arg_count = new_argc;
      args = new_args;
    }

    parse_program_search_args(option, arg_count, args);
  }

  if (arg_count - optind < 1 || arg_count - optind > 2)
    goto free_and_usage;

  for (; optind < arg_count; optind++) {
    if (!first_arg) {
      strcpy(option->pattern, args[optind]);
      first_arg = 1;
    } else {
      strcpy(option->directory, args[optind]);
    }
  }

  free(args);

  if (!dirp) {
    fprintf(stderr, "[ERROR]: could not open directory \"%s\"\n",
            option->directory);
    free_option(option);
    exit(-1);
  }

  closedir(dirp);

  return;

// as label
free_and_usage:
  free_option(option);
  free(args);
  usage(-1);
}

// ok
option_t *create_option(configuration_t *config, int argc, char *argv[]) {
  option_t *opt = (option_t *)calloc(1, sizeof(*opt));

  opt->search_type = GREP_SEARCH;

  strcpy(opt->directory, ".");
  read_config(config, opt);
  parse_args(opt, argc, argv);

  return opt;
}

// ok
void free_option(option_t *opt) {
  if (!opt)
    return;
  if (opt->extension)
    free_list(&opt->extension);

  if (opt->ignore)
    free_list(&opt->ignore);

  if (opt->specific_file)
    free_list(&opt->specific_file);

  if (opt->pcre_compiled)
    pcre_free((void *)opt->pcre_compiled);

  if (opt->p_extra)
    pcre_free((void *)opt->p_extra);

  free(opt);
}